Repository navigation
feat(adk,#14058): measure first 3 EPITA behavioural contracts against real ADK runtime - #14383
Conversation
…inst the real ADK runtime Tranche 1 of the property-oracle decision: EPITA says WHAT must hold, the ADK runtime says whether it is carried. Six tests drive the REAL ADK 2.8 (Runner, InMemorySessionService, auto-function-calling, real dataset_profile tool); only the LLM is scripted - the contract tests the runtime mechanics, not model output. Contracts measured: - C1 session isolation: carried (state never leaks between sessions); falsified under simulated fusion. The DURABLE cross-turn shared state is NOT carried (run_agent_turn builds a fresh service per call) - measured, and the porting is a grain above ADK, never an SK restore. - C2 tool round-trip: carried, and the response leg carries the value computed by the REAL tool (960 cells), not a model echo. - C3 turn budget: carried via asyncio.wait_for + RECOVERABLE-LOCAL verdict; ADK's own max_llm_calls hard cap is measured reachable through RunConfig. Acceptance criterion 4 verified at removal: mutations C2 (response relay removed), C3 (wait_for neutralised) and C1 (fused sessions) each make their test FAIL - journal in the PR body. Existing runtime tests stay green (13/13). Co-Authored-By: Claude-Code <noreply@anthropic.com>
|
G-VAR-2 light cap reached (advisory, non bloquant). |
|
G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels |
jsboige
left a comment
There was a problem hiding this comment.
[Hermes] — CoursIA #14383 contrats comportementaux ADK 2.8 (#14058 tranche 1). Lecture complète du fichier (+281, nouveau test-only, runtime byte-identique).
Vérifié :
- L'oracle est bien un oracle : seul le LLM est scripté, Runner/InMemorySessionService/auto-function-calling/outil réel
dataset_profileexécutés. C2 vérifié au niveau événements ADK — la jambe réponse porte la valeur calculée (dataset_profile(120, 8)→ 960 cellules, 15.0), pas un écho. - Mutation journal au retrait (critère 4) : 3/3 FAILED sous mutation, et surtout la note d'instrument sur le test C3 v1 qui restait vert (réponse vide → catch-all → AdkRuntimeUnavailable pour la mauvaise raison) corrigée par un fake qui répond après la pause — c'est exactement la discipline anti-faux-négatifs attendue.
test_state_does_not_persist_across_callsnommé comme mesure du non-portage, avec instruction d'inversion — bon pattern de contrat documenté négativement.
Concerns (non bloquants, tranches suivantes / attestation) :
test_c3_llm_call_budget_is_a_real_adk_mechanic:pytest.raises(Exception)+assert "limit" in str(...)est large — uneLlmCallsLimitExceededErrormais aussi un échec de config/session ADK non lié au plafond satisferait l'assert. Serrer surgoogle.adk.runners.LlmCallsLimitExceededError(import direct) ou au moins matcher le nom d'exception en plus du message.- La mesure du non-portage de persistance (C1) se fait via
run_agent_turn(wrapper qui instancie un service frais par appel) — le contrat EPITA « état durable entre tours » n'est donc pas mesuré comme non-porté par ADK lui-même (un Runner + service partagé entre appels porterait l'historique), mais comme non-porté par le wrapper. Nuance à formuler telle quelle dans l'attestation #14058, sinon elle survendendrait le non-portage au runtime ADK.
Security scan : 0 match. 6/6 + 13/13 revendiqués, non rejoués ici (oracle d'exécution = tranches suivantes ; les guards CI ne couvrent pas Track2).
Grain: MED/test — lane myia-po-2024:CoursIA — prev: MED/tooling #14378
See #14058 (tranche 1, steer ai-01 du 2026-09-02) — l'issue reste ouverte : tranches suivantes (contrats restants : désignation, handoff, spécialistes) + liste complète attestée.
Ce que fait cette PR
Tranche 1 de l'oracle de propriétés #14058 : 6 tests mesurant les 3 premiers contrats comportementaux EPITA contre le VRAI runtime ADK 2.8 — Runner, InMemorySessionService, auto-function-calling et exécution de l'outil réel
dataset_profile. Seul le LLM est scripté (unBaseLlmde test qui rejoue des réponses) : le contrat teste la mécanique du runtime, pas les réponses d'un modèle — c'est la définition même de l'oracle.Nouveau fichier unique : utils/test_adk_runtime_contracts.py, à côté des 13 tests existants du runtime (même conftest, même régime d'invocation locale — la série n'est câblée dans aucun workflow CI, les tests Track2 s'exécutent par invocation explicite documentée).
Les 3 contrats, mesurés
run_agent_turnavec le mêmesession_idne partagent rien — le wrapper instancie un service frais à chaque appel ; l'historique du tour 1 n'atteint jamais lellm_requestdu tour 2 (observable capturé). Le portage de la persistance est un grain au-dessus d'ADK (jamais une restauration SK) — il inverseratest_state_does_not_persist_across_calls, nommé comme tel.function_callETfunction_responseexposés parAdkRunResult; et la jambe réponse porte la valeur calculée par l'outil réel (dataset_profile(120, 8)→ 960 cellules, 15.0 lignes/colonne) — pas un écho du modèle.asyncio.wait_for(timeout_seconds)lèveAdkRuntimeUnavailableverdictRECOVERABLE-LOCALà expiration. EtRunConfig(max_llm_calls=2)coupe réellement une boucle de 3 appels (LlmCallsLimitExceededError) — le budget d'appels est une mécanique ADK joignable, pas une abstraction.Critère 4 de l'acceptance — vérifié AU RETRAIT (mutations locales)
Un contrat n'est réputé porté que si son test échoue quand on le retire. Chaque mutation a été appliquée au runtime, le test ciblé relancé, puis la mutation revertée :
tool_responses(collecte remplacée parpass)test_c2_tool_round_trip_exposes_both_legsasyncio.wait_for(consume_events(), timeout=...)neutralisé enawait consume_events()test_c3_turn_budget_raises_recoverable_local_on_timeoutsession-b→ même id quesession-a)test_c1_sessions_isolate_stateNote d'instrument (vérifiée en cours de route) : la première version du test C3 restait verte sous mutation — le script « sleep » ne yieldait rien, et l'
AdkRuntimeUnavailableobservé venait du catch-all du runtime sur réponse vide, pour la mauvaise raison. Le fake yield désormais une réponse finale après la pause, ce qui rend la mutation discriminante. Un garde de ce type se valide par ses faux négatifs (anti-regression).Validation
python -m pytest utils/test_adk_runtime_contracts.py(depuisTrack2-GoogleADK/) → 6/6 OK (~7 s)python -m pytest utils/test_adk_runtime.py→ 13/13 OK (tests existants intacts)google-adk==2.8.0installé ce jour (règle F ; la chaîne litellm 1.83.14 / openai 2.24.0 / pydantic 2.12.5 était déjà en place)Hors scope / résiduel
adk_runtime.pybyte-identique à main).